/**
 *  ||==\\ ==|| \\===
 *  ||  || ==||  \==/
 *  ||==// ==|| ===// (www.d3s.net)
 * 
 * Max3DS is a part of the D3S Papervision3D Importer Library
 * Please feel free to use, modify, and distribute as long as 
 * as credit is given to the original creator me, Philippe Ajoux.
 * If you have any comments, suggests, requests, or found a bug
 * please send me an e-mail and let me know. You will help everyone.
 * 
 * -----[2] 04.29.07 --------------
 * Update/Revised class to work for [Revision 62] of Papervision3D
 * Cleaned up comments to be more clear and clean
 * Code was also facelifted a bit
 * Added more description of actually parsing of format
 * 
 * -----[1] 01.09.07 --------------
 * Original release that worked with original [Revision ?] of Papervision3D
 * (Revision # unknown, but it was first "closed" beta release)
 * 
 * @version 04.29.07
 * @author Philippe Ajoux (philippe.ajoux@gmail.com)
 */
package d3s.net.papervision3d.objects
{
	import flash.events.Event;
	import flash.events.IOErrorEvent;
	import flash.net.URLLoader;
	import flash.net.URLLoaderDataFormat;
	import flash.net.URLRequest;
	import flash.utils.ByteArray;
	import flash.utils.Endian;
	
	import org.papervision3d.Papervision3D;
	import org.papervision3d.core.NumberUV;
	import org.papervision3d.core.geom.Face3D;
	import org.papervision3d.core.geom.Mesh3D;
	import org.papervision3d.core.geom.Vertex3D;
	import org.papervision3d.core.proto.MaterialObject3D;
	import org.papervision3d.core.proto.SceneObject3D;
	
	/**
	 * The Max3DS DisplayObject3D class lets you load a 3D Studio Max 3DS (.3ds) file.
	 * <p>
	 * The 3DS file format is another popular model format and it is represented in binary
	 * rather than ASCII (like ASE and Wavefront) which results in much smaller file sizes.
	 * <p>
	 * <b>NOTE:</b> Only the object data (vertices, texture coordinates, faces) are processed
	 * 
	 * @version 04.29.07
 	 * @author Philippe Ajoux (philippe.ajoux@gmail.com)
	 */
	public class Max3DS extends Mesh3D
	{
		// [internal] A factor that scales vertices a load-time
		private var internalScale:Number;
		
		// [internal] Path to 3DS file that is being loaded
		private var file:String;
		
		// [internal] A loader that will load the 3DS file
		private var loader:URLLoader;
		 
		/**
		 * Max3DS class lets you load in a 3D Studio Max (.3ds) file.
		 * The name of object in 3DS file will be loaded and applied to this object.
		 * 
		 * @param material
		 * @param filename 	The file to be loaded
		 * @param scale		Any custom scaling to apply when loading file
		 * @param initObject
		 */
		public function Max3DS(material:MaterialObject3D, filename:String, scale:Number = 1, initObject:Object = null)
		{
			super(material, new Array(), new Array(), null, initObject);
			internalScale = scale;
			file = filename;
			load3DS(filename);
		}
		
		/**
		 * Actually load the file using a URLLoader instance
		 * 
		 * @param filename	Path to 3DS file to load
		 */
		private function load3DS(filename:String):void
		{
			loader = new URLLoader();
			loader.dataFormat = URLLoaderDataFormat.BINARY;
			loader.addEventListener(Event.COMPLETE, parse3DS);
			
			try
			{
	            loader.load(new URLRequest(filename));
			}
			catch(e:Error)
			{
				Papervision3D.log("Error in loading 3DS file (" + filename + "): \n" + e.message + "\n" + e.getStackTrace());
			}
		}
		
		/**
		 * 3DS Max Binary File Format (Summary/Short/Abridged)
		 * ---------------------------------------------------
		 * 3DS is a binary file format in LITTLE ENDIAN that consists
		 * of "blocks" of data which contains the object data:
		 * 		vertices, faces, texture coordinates, etc...
		 * 
		 * This format is very large, complex, and proprietary.
		 * Thus, not everything about it is known, and what is below has
		 * been determined through "reverse engineering" and experimentation.
		 * 
		 * Blocks are defined by ID's and have a length. Only some blocks
		 * are used in the parser, described below:
		 * 
		 * MAIN_CHUNK, EDITOR_CHUNK, TRIANGLE_MESH, Other
		 * 		All skipped, contents unknown or not needed
		 * 
		 * OBJECT_BLOCK
		 * 		Contains the object name encoded in bytes (binary 0 ending) 
		 * 
		 * VERTEX_BLOCK
		 * 		List of vertices in [float,float,float] format
		 * 
		 * FACE_BLOCK
		 * 		List of all faces in [int,int,int,int] where [vertex id, vertex id, vertex id, flags]
		 * 		The flags are unknown and ignored
		 * 
		 * TEXTURE_VERTEX_BLOCK
		 * 		UV coordinates as a parrallel vertex array in [float,float] format
		 */
		private function parse3DS(event:Event):void
		{
			// New revision's of Papervision3D have vertex and face data in a "Geometry" object.
			// I don't really understand this at all, but I think it is used so object share 
			// data and overall less memory usage. It's annoying. LAMEZ ;)
			var vertices:Array = geometry.vertices;
			var faces:Array = geometry.faces;
			
			// Temporary arrays for faces and uv coordinates
			var tempFaces:Array = new Array();
			var uvs:Array = new Array();
			
			// Other variables used in the parsing process
			var chunkID:uint, chunkLength:uint;
			var data:ByteArray = loader.data;
			var i:uint, size:uint;
			var buffer:String;
			var face:Object;
			
			// Endian needed to read in the binary data
			// MAKE SURE TO DO THIS, OR YOU WILL HATE LIFE (I did)
			data.endian = Endian.LITTLE_ENDIAN;
			
			while (data.bytesAvailable > 0)
			{
				chunkID = data.readUnsignedShort();
				chunkLength = data.readUnsignedInt();
				
				switch (chunkID)
				{
				// MAIN_CHUNK, EDITOR_CHUNK, TRIANGLE_MESH_BLOCK (ignored)
				// NOTE: We have to break because these blocks don't actually contain
				// data, but are more of header indicators, and thus don't have a length
				// so we need to just ignore them
				case 0x4d4d: break;
				case 0x3d3d: break;
				case 0x4100: break;
				
				// OBJECT_BLOCK
				//		We read in the object name using a temporary buffer
				case 0x4000:
					buffer = "";
					while ((size = data.readByte()) != 0)
						buffer += String.fromCharCode(size);
					name = buffer;
					break;
				
				// VERTEX_BLOCK 
				case 0x4110:
					size = data.readUnsignedShort();
					for (i = 0; i < size; i++)
						vertices.push(new Vertex3D(
							 data.readFloat() * internalScale, 
							 data.readFloat() * internalScale,
							 data.readFloat() * internalScale
					    ));
					break;
					
				// FACE_BLOCK
				case 0x4120:
					size = data.readUnsignedShort();
					for (i = 0; i < size; i++)
						tempFaces.push(
						{
							a: data.readUnsignedShort(),
							b: data.readUnsignedShort(),
							c: data.readUnsignedShort(),
							flags: data.readUnsignedShort()
						});
					break;
				
				// TEXTURE_VERTEX_BLOCK
				case 0x4140:
					size = data.readUnsignedShort();
					for (i = 0; i < size; i++)
						uvs.push(new NumberUV(data.readFloat(), data.readFloat()));
					break;
						
				// Other blocks are just skipped completely by their length
				default:
					data.position += chunkLength - 6;
				}
			}
			
			// Actually add the faces with the proper UV coordinate texturing
			// NOTE: We actually support loading files that have no texturing!
			for each (face in tempFaces)
				if (uvs.length != vertices.length)
					// Add faces without any texturing
					faces.push(new Face3D([vertices[face.c], vertices[face.b], vertices[face.a]], null));
				else
					// Add faces WITH texture mapping
					faces.push(new Face3D([vertices[face.c], vertices[face.b], vertices[face.a]], 
									  	  null, [uvs[face.c], uvs[face.b], uvs[face.a]]));
			
			// Close up shop and display some nice debuging information
			loader.close();
			Papervision3D.log("__________________________________" + 
							  "\n_______Parsed 3DS_________________" +
							  "\n|" + 
							  "\n|\tfile: " + file +
							  "\n|\tvertices:" + vertices.length +
							  "\n|\ttexture vertices:" + uvs.length +
							  "\n|\tfaces:" + faces.length +
							  "\n|_________________________________"
							  );
		}
	}
}